package com.hm.health.BLE;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.goodix.comsdk.entity.MeasureResult;
import com.goodix.comsdk.errorcode.IErrorCode;
import com.goodix.comsdk.sdk.OnHBDDataListener;
import com.goodix.iot.libbase.logger.Glog;
import com.goodix.iot.libbase.utils.ArrayUtil;
import com.goodix.iot.libcommounication.connection.constant.BleGattServices;
import com.goodix.iot.sdk.impl.exception.ParseError;
import com.goodix.iot.sdk.impl.cardiff.HeartRateServiceDecoder;
import com.goodix.iot.sdk.impl.cardiff.IntArrayPacket;
import com.goodix.libromablecom.sdk.cardiff.arguments.StartArgumentsKeyEnum;
import com.hm.health.MyApplication;
import com.polidea.rxandroidble.RxBleClient;
import com.polidea.rxandroidble.RxBleConnection;
import com.polidea.rxandroidble.RxBleDevice;
import com.polidea.rxandroidble.exceptions.BleCharacteristicNotFoundException;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

/**
 * 标准心率设备，例如Polar，Jabra
 *
 * @author chenshi
 * @date 2017 /3/17
 */
public class HrsDevice {

    private static final String TAG = "HrsDevice";

    private static final int MSG_DEVICE_DISCONNECTED = 0x01;
    public static final int MSG_DEVICE_RECONNECT = 0x02;

    private static final long DELAY_TIME_SEND_DISCONNECT_MSG = 500;
    public static final int COUNT_RETRY = 3;
    public static final int DELAY_RECONNECT_DEVICE = 2000;
    public static final int DELAY_CONNECT_TIMEOUT = 8000;

    private RxBleClient mRxBleClient;
    private AtomicBoolean mIsConnected = new AtomicBoolean(false);
    private OnDeviceStatus mOnDeviceStatus;
    private Subscription mSubscription;
    private RxBleDevice mRxBleDevice;
    private Subscription mSubscriptionConnectionState;
    private MsgHandler mHandler;
    private int[] mHrs;
    private String mAddress;
    private boolean mStopMeasure = true;
    private int mRetryCount = 0;
    private OnHBDDataListener mOnHBDDataListener;
    private List<String> mHRFunctions = new ArrayList<>();

    /**
     * Sets on device status.
     *
     * @param onDeviceStatus the on device status
     */
    public void setOnDeviceStatus(OnDeviceStatus onDeviceStatus) {
        mOnDeviceStatus = onDeviceStatus;
    }

    /**
     * Is connected boolean.
     *
     * @return the boolean
     */
    boolean isConnected() {
        if (null != mRxBleDevice) {
            return mRxBleDevice.getConnectionState()
                    == RxBleConnection.RxBleConnectionState.CONNECTED;
        } else {
            return false;
        }
    }

    /**
     * Instantiates a new Hrs device.
     *
     * @param context the context
     */
    public HrsDevice(Context context) {
        mRxBleClient = RxBleClient.create(context);
        mHandler =  new MsgHandler(Looper.getMainLooper(), this);
        mStopMeasure = true;
        mRetryCount = 0;
        mOnHBDDataListener = (OnHBDDataListener) MyApplication.getHBDTestClient();
        initFunctionList();
    }

    private void initFunctionList() {
        mHRFunctions.add(StartArgumentsKeyEnum.AppFunction_ECG);
    }

    /**
     * Connect.
     *
     * @param address the address
     */
    public void connect(String address) {
        disconnect();

        if (null != mOnDeviceStatus) {
            mOnDeviceStatus.onConnecting(address, IErrorCode.SUCCESS);
        }

        mStopMeasure = false;
        mAddress = address;
        mRxBleDevice = mRxBleClient.getBleDevice(mAddress);
        mSubscription = mRxBleClient.getBleDevice(mAddress).establishConnection(false)
                .doOnSubscribe(new Action0() {
                    @Override
                    public void call() {
                        observeConnectionStateChanges(mAddress);
                    }
                }).flatMap(new Func1<RxBleConnection, Observable<Observable<byte[]>>>() {
                    @Override
                    public Observable<Observable<byte[]>> call(RxBleConnection rxBleConnection) {
                        return rxBleConnection.setupNotification(BleGattServices.HEART_RATE_MEASUREMENT);
                    }
                }).observeOn(AndroidSchedulers.mainThread())
                .doOnNext(new Action1<Observable<byte[]>>() {
                    @Override
                    public void call(Observable<byte[]> observable) {
                        mIsConnected.set(true);
                        if (null != mOnDeviceStatus) {
                            mOnDeviceStatus.onInitialized(mAddress, IErrorCode.SUCCESS);
                        }
                    }
                }).flatMap(new Func1<Observable<byte[]>, Observable<byte[]>>() {
                    @Override
                    public Observable<byte[]> call(Observable<byte[]> observable) {
                        return observable;
                    }
                }).observeOn(Schedulers.io()).subscribe(new Action1<byte[]>() {
                    @Override
                    public void call(byte[] bytes) {
                        handelHrsRawData(mAddress, bytes);
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        Log.e(TAG, "call: ", throwable);
                        if (throwable instanceof BleCharacteristicNotFoundException) {
                            disconnect();
                            if (null != mOnDeviceStatus) {
                                mOnDeviceStatus.onDisconnected(mAddress, IErrorCode.SUCCESS);
                            }
                        }
                    }
                });
    }

    /**
     * 处理当前设备接收的标准心率服务原始数据
     * @param strAddress  address of device
     * @param bytes       datas response from device
     */
    private void handelHrsRawData(String strAddress, byte[] bytes) {
        int[] hrs = parseHrs(bytes);
        Glog.d(TAG, "call: address:" + strAddress + ": " + ArrayUtil.toString(hrs));
        if (null == hrs || hrs.length == 0) {
            return;
        }

        if (hrs.length >= 1) {
            for (String fun : mHRFunctions) {
                upateMeasureResult(fun,StartArgumentsKeyEnum.AppFunction_HR,
                        String.valueOf(hrs[0]), "bpm", new int[0]);
            }
        }
    }

    private void upateMeasureResult(String functionName, String title,
                                    String value, String units, int[] datas) {
        MeasureResult measureResult = new MeasureResult();
        measureResult.mFunctionName = functionName;
        measureResult.mUnit = units;
        measureResult.mTitle= title;
        measureResult.mValue = value;
        measureResult.mProperties = datas;
        measureResult.mDeviceAddress = mRxBleDevice.getMacAddress();
        measureResult.mDeviceName = mRxBleDevice.getName();
        if (null != mOnHBDDataListener) {
            mOnHBDDataListener.onMeasureResult(measureResult);
        }
    }

    /**
     * 监听设备连接状态
     * @param  address  address of device
     */
    private void observeConnectionStateChanges(String address) {
        mSubscriptionConnectionState = mRxBleDevice.observeConnectionStateChanges()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1<RxBleConnection.RxBleConnectionState>() {
                    @Override
                    public void call(RxBleConnection.RxBleConnectionState rxBleConnectionState) {
                        Log.e(TAG, "observeConnectionStateChanges:" + address + " state: " + rxBleConnectionState);
                        if (mSubscriptionConnectionState.isUnsubscribed()) {
                            Glog.d(TAG, "cancel observeConnectionStateChanges");
                            return;
                        }

                        if (rxBleConnectionState.equals(RxBleConnection.RxBleConnectionState.DISCONNECTED)) {
                            // Send disconnect message to user after 500 millisecond
                            if (mHandler.hasMessages(MSG_DEVICE_DISCONNECTED)) {
                                mHandler.removeMessages(MSG_DEVICE_DISCONNECTED);
                            }
                            mHandler.sendEmptyMessageDelayed(MSG_DEVICE_DISCONNECTED, DELAY_TIME_SEND_DISCONNECT_MSG);
                        } else if (rxBleConnectionState.equals(RxBleConnection.RxBleConnectionState.CONNECTING)) {
                            if (mHandler.hasMessages(MSG_DEVICE_DISCONNECTED)) {
                                mHandler.removeMessages(MSG_DEVICE_DISCONNECTED);
                            }
                            // Ble device connection timeout after 8 seconds
                            mHandler.sendEmptyMessageDelayed(MSG_DEVICE_DISCONNECTED, DELAY_CONNECT_TIMEOUT);
                            if (null != mOnDeviceStatus) {
                                mOnDeviceStatus.onConnecting(address, IErrorCode.SUCCESS);
                            }
                        } else if (rxBleConnectionState.equals(RxBleConnection.RxBleConnectionState.CONNECTED)) {
                            if (mHandler.hasMessages(MSG_DEVICE_DISCONNECTED)) {
                                mHandler.removeMessages(MSG_DEVICE_DISCONNECTED);
                            }

                            mRetryCount = 0;
                            if (null != mOnDeviceStatus) {
                                mOnDeviceStatus.onConnected(mAddress, IErrorCode.SUCCESS);
                            }
                        }
                    }
                });
    }

    private static class MsgHandler extends Handler {
        private WeakReference<HrsDevice> mHrsDevice;

        public MsgHandler(Looper looper, HrsDevice hrsDevice) {
            super(looper);
            mHrsDevice = new WeakReference<HrsDevice>(hrsDevice);
        }

        @Override
        public void dispatchMessage(Message msg) {
            super.dispatchMessage(msg);
            HrsDevice hrsDevice = mHrsDevice.get();
            if (null != hrsDevice) {
                hrsDevice.dispatchMessage(msg);
            }
        }
    }

    private void dispatchMessage(Message msg) {
        switch (msg.what) {
            case MSG_DEVICE_DISCONNECTED:
                if (mStopMeasure) {
                    if (null != mOnDeviceStatus) {
                        mOnDeviceStatus.onDisconnected(mAddress, IErrorCode.SUCCESS);
                    }
                } else {
                    mHandler.sendEmptyMessageDelayed(MSG_DEVICE_RECONNECT, DELAY_RECONNECT_DEVICE);
                }
                break;
            case MSG_DEVICE_RECONNECT:
                if ((!mStopMeasure) && (mRetryCount++ > COUNT_RETRY)) {
                    mRetryCount = 0;
                    disconnect();
                    if (null != mOnDeviceStatus) {
                        mOnDeviceStatus.onDisconnected(mAddress, IErrorCode.SUCCESS);
                    }
                } else {
                    connect(mAddress);
                }
                break;
            default:
                break;
        }
    }

    /**
     * 断开设备连接
     */
    public void disconnect() {
        mStopMeasure = true;
        if (null != mSubscription
                && !mSubscription.isUnsubscribed()) {
            mSubscription.unsubscribe();
            mSubscription = null;
        }

        if (null != mSubscriptionConnectionState
                && !mSubscriptionConnectionState.isUnsubscribed()) {
            mSubscriptionConnectionState.unsubscribe();
            mSubscriptionConnectionState = null;
        }
    }

    HeartRateServiceDecoder mHeartRateServiceDecoder = new HeartRateServiceDecoder();
    /**
     * 解析标准心率服务接收的原始数据
     *
     * @param bytes the bytes
     * @return list
     */
    public int[] parseHrs(byte[] bytes) {
        try {
            IntArrayPacket packet = mHeartRateServiceDecoder.decode(bytes);
            if (null != packet) {
                mHrs = packet.getDatas();
            }
        } catch (ParseError parseError) {
            parseError.printStackTrace();
        }
        return mHrs;
    }
}